前言
使用Vue.js构建的单页面应用(SPA - single page application),需要引入很多的库,包括自家的router、vuex等,第三方的axios、loadash等等,不做任何配置打包之后的文件就有10M+,在第一次载入页面的时候需要加载完整的应用代码,会出现长时间的白屏,用户体验极差。
我们的目标就是减少包体积、提升加载速度,同时保证可用性、维护性、不侵入业务代码。
具体的优化方法包括:
诊断与验证
既然我们要做优化,我们必须先知道从哪些方面下手,最后如何去验证我们优化的效果。
- dist目录大小,最终打包的所有文件大小
- webpack-bundle-analyzer,分析包中包含的模块的大小
- 浏览器调试工具,开启Disable Cache,查看客户端加载的资源大小以及速度
主要介绍一下webpack-bundle-analyzer,看官方介绍:
This module will help you:
1.Realize what’s really inside your bundle
2.Find out what modules make up the most of its size
3.Find modules that got there by mistake
4.Optimize it!
效果图
使用方法
Vue CLI 3 默认支持打包报告,其实就是webpack-bundle-analyzer这个插件,使用vue-cli-service build --report
就会在dist目录下生成一个report.html,打开这个页面就可以看到分析报告了。
为了方便使用,我们可以在package.json的scripts下面新增一行:"report": "vue-cli-service build --report"
,这样就可以使用npm run report
命令实现了。
PS:老版本的vue cli可以通过npm run build --report
命令,打包完成后会自动打开分析页面
怎么看这个报告呢?其实很简单,每一个分区代表打包以后的一个js文件,不同的颜色代表了文件的大小,从大到小,分区里面会嵌套分区,表示包对应的子模块;左侧的菜单展示了每个js文件在stat(原始)、parsed(编译后)、gizpped(压缩后)三种情况的大小。
优化前后对比
好不好,看疗效。我们先看结果再看过程。
优化前
- dist目录大小为
13.3MB
- 打包后的本地js文件(/dist/static/js/):
- 浏览器请求到的js文件:
- webpack-bundle-analyzer:
可以看到,实际请求时需要加载两个本地js文件一共1.95M
,还有一个第三方js文件815kb
,合计2.74M
,用时接近10s
(吐槽下公司网络)。
优化后
- dist目录大小为
2.95MB
- 打包后的本地js文件(/dist/static/js/):
- 浏览器请求到的js文件:
- webpack-bundle-analyzer:
可以看到,实际请求时请求了4个本地js文件一共156.5kb
,CDN加载了6个包,一共207.5kb
,合计364kb
,用时2.2s
,注意,优化后是包含了CDN资源的大小,网上很多网上都没有计算CDN的资源。对比优化前体积减少了87%
,加载时间减少了78%
(受网路影响,不太精确)。显而易见,优化效果非常显著,基本实现了秒开页面,用户体验杠杠滴!
优化过程
Vue Router 懒加载
首先看官方文档,是这样描述懒加载的
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
实际操作非常简单,只需要把src/router.js中的组件引入方式从
1 | import Page404 from './src/Page404.vue'; |
变更为1
const page404 = () => import('./views/404.vue');
就这样,over。当然,你想自定义分块也是可以的,通过 命名 chunk可以将多个组件打包到一个异步块中。
1 | const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue') |
最后的效果就是,打包后的js文件由之前的app.js和chunk-vendors.js多出了很多chunk,浏览器访问时会按需加载对应的chunk,从而减少了首屏的加载时间。
CDN引入依赖
CDN引入依赖很简单,使用script标签引入对应的资源即可,但是存在几个问题:
CDN服务器挂了怎么办?
要是我们能检测到CDN是否成功就好了,当然能。常用的依赖包都会创建一个全局变量,通过检测这个环节变量就可以知道CDN的资源是否加载成功,不成功就加载服务器资源就可以了。缺点就是服务器上必须存放一份依赖包的备份,会增加包体积,但是并不影响访问速度。
怎么知道依赖包的全局变量呢?我们用axios举例,打开axios的Github的dist目录,可以看到axios.js和axios.min.js,我们要使用的是min包,但我们要在axios.js中去查找他的全局变量。其中有这样一段代码。
1
2
3
4
5
6
7
8if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["axios"] = factory();
else
root["axios"] = factory();我们就可以知道axios的全局变量就是axios了,在我们的publick/index.html中就可以这样检测
1
2
3<!--引入axios-->
<script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
<script>window.axios || document.write('<script src="<%= BASE_URL %>js/axios.min.js"><\/script>')</script>这样就实现了高可用的CDN引入依赖了,不怕挂!
引入min包,没有了错误提醒,原有的业务代码中使用import引入的方式,CDN引入后无法使用了,维护两份代码?
为了减小包体积提升速度,CDN需要引入min包,但是就损失依赖自带的错误提醒,只适用于生产环境,传统的做法就是维护两分代码,开发版使用完整包 或者import引入,生产环境引入min包,这对于一个懒人来说太麻烦了。
要是能这样就好了,业务代码使用import方式引入依赖,开发环境加载完整包,生产环境使用CDN加载min包,完美!但是,这可能吗?当然可能,使用webpack的externals+html-webpack-plugin就可以轻松搞定啦!
externals的作用就是在使用CDN的情况下,业务代码中仍然可以使用import引入依赖。官方文档是这样说的
防止将某些
import
的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。externals在vue.config.js中的配置webpack
1
2
3
4
5
6
7
8
9
10
11configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.externals = {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios',
'vue-grid-layout': 'VueGridLayout'
};
}
}配置的格式:
import包名: 全局变量
,工作原理就是webpack在读取到import语句时会从externals中去查找对应的全局变量然后加载。比如'vue-router': 'VueRouter'
,业务代码中使用import router from 'vue-router'
,webpack就会去查找VueRouter
这个全局变量。通过
process.env.NODE_ENV === 'production'
判断生产环境才配置externals,开发环境仍然加载npm install
安装的依赖,这样就解决了生产环境使用import引入读取CDN资源的问题,但是开发环境CDN资源仍然会加载,我们并不会用到,这种浪费浏览器资源的事情是不能容忍的,必须干掉!这时候就需要html-webpack-plugin登场了。html-webpack-plugin在Vue CLI 中主要负责处理public/index.html,具体查看文档,在public/index.html中判断运行环境决定是否启用CDN。
1
2
3
4
5<% if (htmlWebpackPlugin.options.environment === 'production') { %>
<!--引入axios-->
<script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>
<script>window.axios || document.write('<script src="<%= BASE_URL %>js/axios.min.js"><\/script>')</script>
<% } %>这个
environment
并不是自带的,需要我们再vue.config.js中进行配置1
2
3
4
5
6
7
8
9// 在htmlWebpackPlugin中增加环境变量,在index.html中使用
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].environment = process.env.NODE_ENV;
return args;
});
}至此,就实现高可用CDN的工程化了,不需要改业务代码,不需要开发环境生产环境两套代码,三分钟解决烦恼!
第三方库按需引入
都有了CDN了,为什么还需要按需引入呢?举个例子,项目中需要使用lodash的deepClone和isEqual两个方法,使用前面的方法引入loadash,虽然有CDN的加持,但是我们仍然需要加载lodash的完整包,为了两个方法就要加载完整包还是有些得不偿失的,这种情况按需引入可以获得更快的加载速度。
具体按需引入的方法,echarts、element-ui等官方文档都有说明,照着操作就可以了。
lodash的按需引入稍微麻烦一些,需要安装babel-plugin-lodash
这个包
1 | npm install babel-plugin-lodash -D |
然后配置babel.config.js
1 | module.exports = { |
引用部分
1 | import lodash from 'lodash'; |
只有通过lodash的.
运算符调用的方法才会被引入
开启Gzip
nginx的Gzip有两种方式,一种是服务器端的Gzip,这个大家都知道,就是每次请求时服务器先压缩再返回资源,对服务器性能有一定消耗;另一种是Gzip_static,就是打包时生成.gz
文件,每次请求时服务器直接返回.gz文件,不消耗服务器性能。两种开启一种就可以了。
服务器端Gzip
很简单,只需要配置一下nginx的配置就可以了
1
2
3
4
5
6
7
8# 开启gzip
gzip on;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU
gzip_comp_level 5;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 256;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;Gzip_static
需要安装compression-webpack-plugin实现打包为.gz文件,然后开启nginx的gzip_static让nginx优先查找文件的.gz版本发送给客户端。
1
npm install compression-webpack-plugin -D
vue.config.js中配置webpack
1
2
3
4
5
6
7
8
9
10
11
12const productionGzipExtensions = ['js', 'css']
configureWebpack: {
plugins: [
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8
})
]
}nginx配置
1
2# 开启gzip_static
gzip_static on;
其他配置
配置vue.config.js关闭生成环境的sourcemap
1
productionSourceMap: false
减少不必要的第三方依赖
安装依赖区分开发模式,只在开发模式使用的依赖使用dev模式安装,例如
1
npm install xxx --save-dev
总结
优化是没有极限的,不同的项目需要采用不同的优化方案,没有最好的,只有最合适的。填坑之路很漫长,但是我相信,办法总比困难多。